﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.IO;
using System.Windows.Forms;
using System.Configuration;
using Tail4Net.Properties;
using System.Collections.Specialized;

namespace LogViewer
{
    public partial class LogViewerForm : Form
    {
        String[] _args = null;

        public LogViewerForm(String[] args)
        {
            InitializeComponent();
            updater = new UpdateTextDelegate(UpdateText);

            _args = args;

        }

        UpdateTextDelegate updater = null;
        object[] updaterParams = new object[2];

        String LogFileName = null; //"C:\\ArcotOfficePlugin.log";

        int prevLength = 0;

        void OpenFile()
        {
            hasChanged = false;

            StreamReader objReader = null;

            FileInfo fileInfo = new FileInfo(LogFileName);

            if (!fileInfo.Exists || fileInfo.Length == 0 || fileInfo.Length == prevLength)
                return;

            using (System.IO.FileStream logReader = new System.IO.FileStream(
             LogFileName, System.IO.FileMode.Open,
            System.IO.FileAccess.Read, System.IO.FileShare.ReadWrite))
            {
                String txt = "";
                bool toAppend = true;
                try
                {
                    byte[] fileBytes = null;
                    int incLength = 0;

                    if (fileInfo.Length > prevLength)
                    {
                        incLength = (int) fileInfo.Length - prevLength;
                    }
                    else
	                {
                        prevLength = 0;
                        incLength = (int) fileInfo.Length;
                        toAppend = false;
	                }

                    fileBytes = new byte[incLength];

                    logReader.Seek(prevLength, SeekOrigin.Begin);

                    var read = logReader.Read(fileBytes, 0, incLength);

                    prevLength = (int) fileInfo.Length;

                    ASCIIEncoding enc = new ASCIIEncoding();

                    txt = enc.GetString(fileBytes, 0, read);

                    //objReader = new StreamReader(logReader); //LogFileName);

                    //s = objReader.ReadToEnd();
                    //File.ReadAllText(LogFileName);

                }
                catch (Exception ex)
                {
                    MessageBox.Show("Error : " + ex);
                }
                finally
                {
                    if (objReader != null)
                        objReader.Close();
                }

                updaterParams[0] = txt;
                updaterParams[1] = toAppend;

                Invoke(updater, updaterParams);
                //textBox1.Text += txt;
            }
        }

        private void HighlightKeywordsAppend(String txt)
        {
            //{\\fonttbl{\\f0\\fnil\\fcharset0 Courier New;}
            //String rtfStart = "{\\rtf1\\ansi\\deff0 " + RtfColorTable + "}\r\n\\viewkind4\\uc1\\pard\\lang1033\\f0\\fs20";
            String rtfStart = "{\\rtf1\\ansi\\ansicpg1252\\deff0" + RtfColorTable + "\r\n\\uc1\\pard\\lang1033\\f0\\fs20 ";

            String rtfEnd = "}\r\n";

            //String rtfTxt = String.Format("{0}{1} ",
            //                    RtfColorTable, txt);

            //String rtfTxt = String.Format("{{\\rtf1\\ansi\\ansicpg1252\\deff0{0}\r\n\\uc1\\pard\\lang1033\\f0\\fs20 {1}}}\r\n",
            //    RtfColorTable, txt);

            StringBuilder sb = new StringBuilder(txt);
            sb.Replace("\r\n", "\\par\r\n");
            sb.Insert(0, rtfStart).Append(rtfEnd);

            var keywords = optionsForm.FilterText.Split(optionsForm.Seperator);
            foreach (var keyword in keywords)
            {
                sb.Replace(keyword, "\\highlight1 " + keyword + "\\highlight0 ");
            }

            textBox1.SelectionStart = textBox1.Text.Length;
            //textBox1.SelectionLength = 0;
            //textBox1.SelectedRtf = sb.ToString();
            textBox1.Rtf = sb.ToString();
        }

        String RtfColorTable
        {
            get
            {
                String colorTable = String.Format("{{\\colortbl ;\\red{0}\\green{1}\\blue{2};}}",
                    optionsForm.FilterColor.R, optionsForm.FilterColor.G, optionsForm.FilterColor.B);

                return colorTable;
            }
        }
        private void HighlightKeywords(bool isOn)
        {
            StringBuilder sb = new StringBuilder(textBox1.Rtf);

            //Get RTF from richtextbox
            //  Thanks to code at http://www.codeproject.com/KB/cs/RTFSyntaxColour.aspx
            string strRTF = textBox1.Rtf;

            if (isOn)
            {
                var keywords = optionsForm.FilterText.Split(optionsForm.Seperator);

                // Search for colour table info. If it exists update it
                int iCTableStart = strRTF.IndexOf("{\\colortbl;");

                if (iCTableStart != -1) //then colortbl exists
                {
                    //find end of colortbl tab by searching
                    //forward from the colortbl tab itself
                    int iCTableEnd = strRTF.IndexOf('}', iCTableStart);

                    //remove the existing colour table
                    sb.Remove(iCTableStart, iCTableEnd - iCTableStart);

                    //now insert new colour table at index of old colortbl tag

                }

                // find index of start of header
                int iRTFLoc = strRTF.IndexOf("\\rtf");
                // get index of where we'll insert the colour table
                // try finding opening bracket of first property of header first
                int iInsertLoc = strRTF.IndexOf('{', iRTFLoc);

                // we'll insert colour table at the beginning of the header
                sb.Replace("{", "{" + RtfColorTable, iInsertLoc, 1);

                foreach (var keyword in keywords)
                {
                    sb.Replace(keyword, "\\highlight1 " + keyword + "\\highlight0 ");
                }
            }
            else
            {
                sb.Replace("\\highlight1 ", "\\highlight0 ");
            }

            textBox1.Rtf = sb.ToString();
        }

        delegate void UpdateTextDelegate(String txt, bool isAppend);
        private void UpdateText(String txt, bool isAppend)
        {
            int startFilterIndex = 0;

            if (isAppend)
            {
                startFilterIndex = textBox1.TextLength;
                
                if (optionsForm.IsFilterEnabled)
                    HighlightKeywordsAppend(txt);
                else
                    textBox1.Text += txt;
            }
            else
            {
                textBox1.Text = txt;
                HighlightKeywords(optionsForm.IsFilterEnabled);
            }

            //StringBuilder sb = new StringBuilder(textBox1.Rtf);

            /*
            int filterLength = filterForm.FilterText.Length;
            int index = textBox1.Text.IndexOf(filterForm.FilterText, startFilterIndex);

            while (index != -1)
            {
                textBox1.Find(filterForm.FilterText, index, index + filterLength, RichTextBoxFinds.None);
                textBox1.SelectionBackColor = filterForm.FilterColor;
                index = textBox1.Text.IndexOf(filterForm.FilterText, index + filterLength);
            }
                */

            //textBox1.Refresh();
            lblStatus.Text = "Size: " + textBox1.TextLength;
        }

        private void Init(String fileName)
        {
            if(String.IsNullOrEmpty(fileName.Trim()))
                return;

            LogFileName = fileName;
            prevLength = 0;
            this.textBox1.Clear();

            this.Text = String.Format("{0} - {1}", LogFileName, Application.ProductName);

            tmrFileWatch.Enabled = true;
            OpenFile();
            ScrollToEnd();
        }

        Tail4NetConf conf = null;

        private void LoadSettings()
        {
            conf = Tail4NetConf.Default;

            if (conf.RecentFile != null)
            {
                Init(conf.RecentFile);
            }

            foreach (var item in conf.Searches)
                if (!tcmbFindText.Items.Contains(item))
                    tcmbFindText.Items.Add(item);

            optionsForm.IsFilterEnabled = conf.FilterEnabled;

            optionsForm.FilterText = conf.FilterText;
            optionsForm.FilterColor = conf.FilterColor;

            mnuOnTop.Checked = this.TopMost = conf.IsAlwaysOnTop;
        }

        private void SaveSettings()
        {
            try
            {
                if (LogFileName != null)
                {
                    conf.RecentFile = LogFileName;
                }

                conf.FilterEnabled = optionsForm.IsFilterEnabled;

                conf.FilterText = optionsForm.FilterText;
                conf.FilterEnabled = optionsForm.IsFilterEnabled;
                conf.FilterColor = optionsForm.FilterColor;
                conf.IsAlwaysOnTop = mnuOnTop.Checked;

                conf.Searches.Clear();

                foreach (var item in tcmbFindText.Items)
                    conf.Searches.Add((string)item);
                
                // Add an Application Setting.
                //conf["ModificationDate"] = DateTime.Now.ToLongTimeString();

                // Save the changes in App.config file.
                conf.Save();
            }
            catch (Exception)
            {
            }
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            LoadSettings();

            try
            {
                String fileName = null;
                if (_args != null && _args.Length > 0 && !String.IsNullOrEmpty(_args[0]))
                    fileName = _args[0];
                else if (!String.IsNullOrEmpty(conf.RecentFile))
                    fileName = conf.RecentFile;

                Init(fileName);
            }
            catch (Exception)
            {
            }

            fswLogWatcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.Size;
            fswLogWatcher.Path = "C:";
            fswLogWatcher.Filter = "Log Files (*.log)|*.log|Text Files (*.txt)|*.txt|All Files (*.*)|*.*";
        }

        private void ScrollToEnd()
        {
            if (textBox1.Text.Length > 0)
            {
                textBox1.SelectionStart = textBox1.Text.Length;
                textBox1.ScrollToCaret();
                //textBox1.Refresh();
            }
        }

        private bool hasChanged = false;

        private void fswLogWatcher_Changed(object sender, FileSystemEventArgs e)
        {
            //fswLogWatcher.EnableRaisingEvents = false;
            if (tbtnAutoRefresh.Checked)
            {
                hasChanged = true;

                if (!bgFileLoader.IsBusy)
                    bgFileLoader.RunWorkerAsync();
            }
        }

        private void bgFileLoader_DoWork(object sender, DoWorkEventArgs e)
        {
            OpenFile();

            //if (hasChanged)
            //    OpenFile();
        }

        private void bgFileLoader_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            fswLogWatcher.EnableRaisingEvents = true;
        }

        private void tmrFileWatch_Tick(object sender, EventArgs e)
        {
            if (LogFileName == null)
            {
                tmrFileWatch.Enabled = false;
                return;
            }

            if (!bgFileLoader.IsBusy)
                bgFileLoader.RunWorkerAsync();
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {
            if (tbtnScrollEnd.Checked)
            {
                ScrollToEnd();
            }
        }

        int findStart = -1;

        private void FindText(bool isBackwards)
        {
            int len = tcmbFindText.Text.Length;

            if (len == 0)
                return;

            tbtnScrollEnd.Checked = false;
            textBox1.SelectionBackColor = textBox1.BackColor;

            var idx = 0; // tcmbFindText.Items.IndexOf(tcmbFindText.Text);
            var searchText = tcmbFindText.Text;

            findStart = textBox1.SelectionStart;

            if (!isBackwards)   //  search forward
            {
                if (findStart == -1)
                    findStart = 0;

                findStart += textBox1.SelectionLength;

                idx = textBox1.Text.IndexOf(searchText, findStart);

                if (idx == -1)
                    idx = textBox1.Text.IndexOf(searchText, 0);

                if (idx >= 0)
                {
                    textBox1.Find(searchText, idx, idx + len, RichTextBoxFinds.None);
                    //textBox1.SelectionBackColor = Color.Yellow;
                    //textBox1.Focus();

                    findStart = idx +len;
                    //start = textBox1.Text.IndexOf(this.textBox1.Text, index) + 1;
                }
                else
                {
                    findStart = -1;
                }
            }
            else
            {
                if (findStart == -1)
                    findStart = textBox1.TextLength;

                idx = textBox1.Text.LastIndexOf(searchText, findStart);

                if (idx == -1)
                    idx = textBox1.Text.LastIndexOf(searchText, textBox1.TextLength);

                if (idx >= 0)
                {
                    textBox1.Find(searchText, idx, idx + len, RichTextBoxFinds.Reverse);
                    //textBox1.Focus();
                    //textBox1.SelectionBackColor = Color.Yellow;
                    //textBox1.ScrollToCaret();

                    findStart = idx;
                    //start = textBox1.Text.IndexOf(this.textBox1.Text, index) + 1;
                }
                else
                {
                    findStart = -1;
                }
            }

            if (idx != -1) //   for a succesful search, save the search word
                if (!tcmbFindText.Items.Contains(searchText))
                    tcmbFindText.Items.Add(searchText);
        }

        private void cmdFindNext_Click(object sender, EventArgs e)
        {
            FindText(false);
        }

        private void cmdFindPrev_Click(object sender, EventArgs e)
        {
            FindText(true);
        }

        private void LogViewerForm_DragDrop(object sender, DragEventArgs e)
        {
            // Extract the data from the DataObject-Container into a string list
            string[] fileList = (string[])e.Data.GetData(DataFormats.FileDrop, false);

            if (fileList.Length >= 1)
            {
                Init(fileList[0]);
            }
        }

        private void LogViewerForm_DragEnter(object sender, DragEventArgs e)
        {
            // Check if the Dataformat of the data can be accepted
            // (we only accept file drops from Explorer, etc.)
            if (e.Data.GetDataPresent(DataFormats.FileDrop))
                e.Effect = DragDropEffects.Copy; // Okay
            else
                e.Effect = DragDropEffects.None; // Unknown data, ignore it
        }

        private void openToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (fdlgOpenLog.ShowDialog() == System.Windows.Forms.DialogResult.OK)
            {
                Init(fdlgOpenLog.FileName);
            }
        }

        private void deleteLogToolStripMenuItem_Click(object sender, EventArgs e)
        {
            if (DialogResult.OK == MessageBox.Show("Are you sure you want to try to delete the log file?", "Log Viewer", MessageBoxButtons.OKCancel))
            {
                try
                {
                    File.Delete(LogFileName);
                    LogFileName = null;
                    textBox1.Text = "";
                }
                catch (Exception err)
                {
                    MessageBox.Show("Error: " + err.Message);
                }
            }
        }

        OptionsForm optionsForm = new OptionsForm();

        private void mnFilter_Click(object sender, EventArgs e)
        {
            if (DialogResult.OK == optionsForm.ShowDialog(this))
            {
                HighlightKeywords(optionsForm.IsFilterEnabled);
            }
            
        }

        AboutBox1 about = new AboutBox1();

        private void mnuAbout_Click(object sender, EventArgs e)
        {
            about.ShowDialog();
        }

        private void LogViewerForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            SaveSettings();
        }

        private void exitToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Close();
        }

        private void tbtnAutoRefresh_Click(object sender, EventArgs e)
        {
            if (tbtnAutoRefresh.Checked)
            {
                fswLogWatcher.EnableRaisingEvents = true;
            }
            else
            {
                fswLogWatcher.EnableRaisingEvents = false;
            }
        }

        private void tbtnScrollEnd_Click(object sender, EventArgs e)
        {
            if (tbtnScrollEnd.Checked)
            {
                ScrollToEnd();
            }
        }

        private void tcmdFindNext_Click(object sender, EventArgs e)
        {
            tcmdFindNext.Checked = true;
            tcmdFindPrev.Checked = false;
        }

        private void tcmdFindPrev_Click(object sender, EventArgs e)
        {
            tcmdFindNext.Checked = false;
            tcmdFindPrev.Checked = true;

        }

        private void tcmdFind_ButtonClick(object sender, EventArgs e)
        {
            FindText(tcmdFindPrev.Checked);
        }

        private void mnuOnTop_Click(object sender, EventArgs e)
        {
            if (mnuOnTop.Checked)
            {
                mnuOnTop.Checked = false;
                this.TopMost = false;
            }
            else
            {
                mnuOnTop.Checked = true;
                this.TopMost = true;
            }
        }

        /*
        private void tcmbFindText_Enter(object sender, EventArgs e)
        {
            FindText(tcmdFindPrev.Checked);
        }
         */

        private void textBox1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.F3)
                FindText(e.Modifiers == Keys.Shift);
        }

        private void copySelectionToolStripMenuItem_Click(object sender, EventArgs e)
        {
            Clipboard.SetText(textBox1.SelectedText);
        }

        private void textBox1_MouseDown(object sender, MouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Middle)
            {
                var searchText = textBox1.SelectedText.Trim();

                if (searchText.Length > 0 && searchText.Length < 100)
                {
                    //if(tcmbFindText.Items.Contains(
                    tcmbFindText.Text = searchText;
                }
            }
        }

        private void tcmbFindText_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyCode == Keys.Enter)
            {
                FindText(tcmdFindPrev.Checked);
            }
        }
    }
}
